package starblaze 
{
  import flash.utils.ByteArray;
	
  internal final class Interpreter 
  {
    
    private var m_stack:ByteArray = null;
    public function Interpreter()
    {
      m_stack = new ByteArray();
      m_stack.length = 128; // initial stack size
    }
    
    [Inline]
    private final function pushInt(i:int):void
    {
      m_stack.writeInt(i);
    }
    
    [Inline]
    private final function pushFloat(f:Number):void
    {
      m_stack.writeFloat(f);
    }
    
    [Inline]
    private final function peekInt():int
    {
      --m_stack.position;
      return m_stack.readInt();
    }
    
    [Inline]
    private final function peekFloat():int
    {
      --m_stack.position;
      return m_stack.readFloat();
    }
    
    [Inline]
    private final function popInt():int
    {
      --m_stack.position;
      var ret:int = m_stack.readInt();
      --m_stack.position;
      return ret;
    }
    
    [Inline]
    private function popFloat():Number
    {
      --m_stack.position;
      var ret:Number = m_stack.readFloat();
      --m_stack.position;
      return ret;
    }
    
    public function run(bytecode                :ByteArray, 
                        outputStream            :ByteArray, 
                        internalStream          :ByteArray, 
                        deathFlagStream         :ByteArray, 
                        constantStream          :ByteArray, 
                        variableStream          :ByteArray, 
                        outputStreamBaseIndex   :uint     , 
                        internalStreamBaseIndex :uint     , 
                        deathFlagStreamBaseIndex:uint     ):void
    {
      // reset stack
      m_stack.position = 0;
      
      var i:int;
      var f:Number;
      while (bytecode.bytesAvailable > 0)
      {
        switch (bytecode.readUnsignedInt())
        {
          // no-op
          case Opcode.NOP   :
            break;
          
          // load data       
          case Opcode.LDCI  : // from constant
            constantStream.position = popInt();
            pushInt(constantStream.readInt());
            break;
          case Opcode.LDCF  : // from constant
            constantStream.position = popInt();
            pushFloat(constantStream.readFloat());
            break;
          case Opcode.LDVI  : // from variable
            variableStream.position = popInt();
            pushInt(variableStream.readInt());
            break;
          case Opcode.LDVF  : // from variable
            variableStream.position = popInt();
            pushFloat(variableStream.readFloat());
            break;
          case Opcode.LDOAI : // from output stream attribute
            outputStream.position = outputStreamBaseIndex + popInt();
            pushInt(outputStream.readInt());
            break;
          case Opcode.LDOAF : // from output stream attribute
            outputStream.position = outputStreamBaseIndex + popInt();
            pushFloat(outputStream.readFloat());
            break;
          case Opcode.LDIAI : // from internal stream attribute
            internalStream.position = internalStreamBaseIndex + popInt();
            pushInt(internalStream.readInt());
            break;
          case Opcode.LDIAF : // from internal stream attribute
            internalStream.position = internalStreamBaseIndex + popInt();
            pushFloat(internalStream.readFloat());
            break;
          // load literal    
          case Opcode.LTI   :
            pushInt(bytecode.readInt());
            break;
          case Opcode.LTF   :
            pushFloat(bytecode.readFloat());
            break;
            
          // copy stack top  
          case Opcode.CPYI  :
            pushInt(peekInt());
            break;
          case Opcode.CPYF  :
            pushFloat(peekFloat());
            break;
            
          // move            
          case Opcode.MOVVI : // to variable
            variableStream.position = popInt();
            variableStream.writeInt(popInt());
            break;
          case Opcode.MOVVF : // to variable
            variableStream.position = popFloat();
            variableStream.writeFloat(popFloat());
            break;
          case Opcode.MOVOAI: // to output stream attribute
            outputStream.position = outputStreamBaseIndex + popInt();
            outputStream.writeInt(popInt());
            break;
          case Opcode.MOVOAF: // to output stream attribute
            outputStream.position = outputStreamBaseIndex + popInt();
            outputStream.writeFloat(popFloat());
            break;
          case Opcode.MOVIAI: // to output stream attribute
            internalStream.position = internalStream + popInt();
            internalStream.writeInt(popInt());
            break;
          case Opcode.MOVIAF: // to output stream attribute
            internalStream.position = internalStream + popInt();
            internalStream.writeFloat(popFloat());
            break;
            
          // add             
          case Opcode.ADDI  :
            pushInt(popInt() + popInt());
            break;
          case Opcode.ADDF  :
            pushFloat(popFloat() + popFloat());
            break;
            
          // sub             
          case Opcode.SUBI  :
            i = popInt();
            pushInt(popInt() - i);
            break;
          case Opcode.SUBF  :
            f = popFloat();
            pushFloat(popFloat() - f);
            break;
            
          // mul             
          case Opcode.MULII :
            pushInt(popInt() * popInt());
            break;
          case Opcode.MULIF :
            f = popFloat();
            pushFloat(popInt() * f);
            break;
          case Opcode.MULFI :
            i = popInt();
            pushFloat(popFloat() * i);
            break;
          case Opcode.MULFF :
            pushFloat(popFloat() * popFloat());
            break;
            
          // div             
          case Opcode.DIVII :
            i = popInt();
            pushInt(popInt() / i);
            break;
          case Opcode.DIVIF :
            f = popFloat();
            pushFloat(popInt() / f);
            break;
          case Opcode.DIVFI :
            i = popInt();
            pushFloat(popFloat() / i);
            break;
          case Opcode.DIVFF :
            f = popFloat();
            pushFloat(popFloat() / f);
            break;
            
          // func            
          case Opcode.RND   :
            pushFloat(Math.random());
            break;
          case Opcode.SIN   :
            pushFloat(Math.sin(popFloat()));
            break;
          case Opcode.COS   :
            pushFloat(Math.cos(popFloat()));
            break;
          case Opcode.TAN   :
            pushFloat(Math.tan(popFloat()));
            break;
          case Opcode.EXP   :
            pushFloat(Math.exp(popFloat()));
            break;
          case Opcode.ASIN  :
            pushFloat(Math.asin(popFloat()));
            break;
          case Opcode.ACOS  :
            pushFloat(Math.acos(popFloat()));
            break;
          case Opcode.ATAN  :
            pushFloat(Math.atan(popFloat()));
            break;
          case Opcode.ATAN2 :
            f = popFloat();
            pushFloat(Math.atan2(f, popFloat()));
            break;
          case Opcode.MULQ  :
            // TODO
            break;
          case Opcode.LERP  :
            // TODO
            break;
          case Opcode.SLERP :
            // TODO
            break;
                             
          // jump            
          case Opcode.JMP   :
            bytecode.position += popInt();
            break;
          case Opcode.JMPZI :
            if (popInt() == 0)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPNZI:
            if (popInt() != 0)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPLTI:
            i = popInt();
            if (popInt() < i)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPLEI:
            i = popInt();
            if (popInt() <= i)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPEQI:
            i = popInt();
            if (popInt() == i)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPGTI:
            i = popInt();
            if (popInt() > i)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPGEI:
            i = popInt();
            if (popInt() >= i)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPZF :
            if (popFloat() == 0.0)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPNZF:
            if (popFloat() != 0.0)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPLTF:
            f = popFloat();
            if (popFloat() < f)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPLEF:
            f = popFloat();
            if (popFloat() <= f)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPEQF:
            f = popFloat();
            if (popFloat() == f)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPGTF:
            f = popFloat();
            if (popFloat() > f)
              bytecode.position += popInt();
            else
              popInt();
            break;
          case Opcode.JMPGEF:
            f = popFloat();
            if (popFloat() >= f)
              bytecode.position += popInt();
            else
              popInt();
            break;
            
          // kill particle   
          case Opcode.KILL  :
            deathFlagStream.position = deathFlagStreamBaseIndex;
            deathFlagStream.writeBoolean(true);
            break;
        }
      }
    }
    
  }

}
